#include "XiolControllerEnumerationTrigger.h"

#include <assert.h>
#include <algorithm>
#include "XiolTime.h"

namespace Xiol
{

/*
	WindowsHookEnumerationTrigger
*/
HHOOK WindowsHookEnumerationTrigger::mHookHandle = 0;
std::vector<WindowsHookEnumerationTrigger*> WindowsHookEnumerationTrigger::mTriggerInstances;

WindowsHookEnumerationTrigger::WindowsHookEnumerationTrigger()
	: mEnumerationNeeded(false)		
{
	// We initialize mEnumerationNeeded to true so a first enumeration is triggered immediately 
	// (i.e. the first time enumerationNeeded() is called), rather than having to wait 
	// for a first system-wide WM_DEVICECHANGE message
	mEnumerationNeeded = true;

	if ( mTriggerInstances.empty() )
	{
		DWORD threadID = GetCurrentThreadId();
		HINSTANCE hInstance = GetModuleHandle(NULL) ;
		mHookHandle = SetWindowsHookEx( WH_CALLWNDPROC, wndProcHook, hInstance, threadID );
		assert( mHookHandle );
	}
	mTriggerInstances.push_back(this);
}

WindowsHookEnumerationTrigger::~WindowsHookEnumerationTrigger()
{
	std::vector<WindowsHookEnumerationTrigger*>::iterator itr;
	itr = std::find( mTriggerInstances.begin(), mTriggerInstances.end(), this );
	assert( itr!=mTriggerInstances.end() );
	mTriggerInstances.erase(itr);
	
	if ( mTriggerInstances.empty() )
	{
		BOOL ret = UnhookWindowsHookEx( mHookHandle );
		assert( ret );
		mHookHandle = 0;
	}
}

bool WindowsHookEnumerationTrigger::enumerationNeeded()
{
	bool ret = mEnumerationNeeded;
	if ( mEnumerationNeeded )
		mEnumerationNeeded = false;
	return ret;
}

LRESULT CALLBACK WindowsHookEnumerationTrigger::wndProcHook( int nCode, WPARAM wParam, LPARAM lParam )
{
	if ( nCode<0 )  // Do not process message 
		return CallNextHookEx( NULL, nCode, wParam, lParam );

	if ( wParam!=0 ) 
	{
		const CWPSTRUCT& params = *reinterpret_cast<CWPSTRUCT*>(lParam);
		if ( params.message==WM_DEVICECHANGE )
		{
			for ( std::size_t i=0; i<WindowsHookEnumerationTrigger::mTriggerInstances.size(); ++i )
				WindowsHookEnumerationTrigger::mTriggerInstances[i]->mEnumerationNeeded = true;
		}
	}

	// Process event
	return CallNextHookEx( NULL, nCode, wParam, lParam );
}

/*
	TimeBasedEnumerationTrigger
*/
TimeBasedEnumerationTrigger::TimeBasedEnumerationTrigger( unsigned int intervalInMs )
	: mIntervalInMs(intervalInMs),
	  mStartTime(0),
	  mNextTime(0)
{
	mStartTime = Xiol::Time::getTimeAsMilliseconds();
	
	// At startup, we schedule the enumeration as soon as possible,
	// instead of waiting an initial interval like so:
	// updateNextTime();
	mNextTime = mStartTime;
}

bool TimeBasedEnumerationTrigger::enumerationNeeded()
{
	bool ret = false;
	unsigned int currentTime = Xiol::Time::getTimeAsMilliseconds();
	if ( currentTime>=mNextTime )
	{
		ret = true;
		updateNextTime();
	}
	return ret;
}

// Return the number of intervals done since start. 
// Returns -1 if interval was set to 0 (continuous firing)
int TimeBasedEnumerationTrigger::updateNextTime()
{
	int numIntervalsDone = -1;
	unsigned int currentTime = Xiol::Time::getTimeAsMilliseconds();
	if ( mIntervalInMs==0 )
	{
		mNextTime = currentTime;
	}
	else
	{
		unsigned int timeSinceStart = currentTime - mStartTime;
		numIntervalsDone = timeSinceStart / mIntervalInMs;
		assert( numIntervalsDone>=0 );
		mNextTime = mStartTime + (static_cast<unsigned int>(numIntervalsDone) + 1) * mIntervalInMs;
	}
	return numIntervalsDone;
}

}

